import random
import math

class GlobalState:
    # player, warp, popUps, page, absoluteDay
    
    def __init__(self):
        self.player = Player()
        self.absolute_day = 1

class Player:
    def __init__(self):
        self.credits = 1500
        self.commodities = {}
        self.ship = Ship("Llama")
        self.planet = get_planet("Earth")

global_state = None

def new_game():
    global_state = GlobalState()

    for commodity in commodity_list:
        global_state.player.commodities[commodity.name] = 0

    global_state.change_cursor("Commodities")

    for planet in planet_list:
        planet.coordinates.x = planet.starting_coordinates.x
        planet.coordinates.y = planet.starting_coordinates.y

    global_state.absolute_day = 1
    days = 366
    pass_days(days)

    return True

def perform_attack(ship_1, ship_2):
    hit_chance = 0.75
    if (random.random() < hit_chance):
        ship_2.hull_health = ship_2.hull_health - ship_1.attack

def get_planet_news(planet_id):
    planet = get_planet(planet_id)
    result = []
    for commodity in commodity_list:
        if (planet.commodity_rates[commodity.name] < 0.67):
            result.append(commodity.cheap_text)
        elif (planet.commodity_rates[commodity.name] > 1.5):
            result.append(commodity.expensive_text)
    if (len(result) == 0):
        result.append("I haven't heard news from here.")
    return result[0:3]

def revolve_planet(planet, days):
    distance = calculate_planet_distance(planet, sun)
    x_1 = planet.coordinates.x
    y_1 = planet.coordinates.y
    center_x = sun.coordinates.x
    center_y = sun.coordinates.y
    delta_theta = 6.283185307179586 * (days / planet.year_time)
    theta_1 = math.atan((y_1 - center_y) / (x_1 - center_x))
    theta_2 = theta_1 + delta_theta
    delta_y_2 = distance * math.sin(theta_2)
    delta_x_2 = distance * math.cos(theta_2)
    planet.coordinates.x = center_x + delta_x_2
    planet.coordinates.y = center_y + delta_y_2

def update_commodity_rates(planet, days):
    update_probability = 1 - (0.8409 ** days)
    for commodity in commodity_list:
        if (random.random() < update_probability):
            new_rate = 3 ** (random.random() * 2 - 1)
            new_amount = math.floor((3 ** (random.random() * 2 - 1)) * commodity.base_stock)
            planet.commodity_rates[commodity.name] = new_rate
            planet.commodities[commodity.name] = new_amount

def pass_days(num_days):
    for planet in planet_list:
        revolve_planet(planet, num_days)
        update_commodity_rates(planet, num_days)
    global_state.absolute_day = global_state.absolute_day + num_days

class Ship:
    def __init__(self, ship_id):
        model_info = ship_list[ship_id]
        self.model = ship_id
        self.cost = model_info.cost
        self.hullHealth = model_info.hull_strength
        self.hullStrength = model_info.hull_strength
        self.attack = model_info.attack
        self.speed = model_info.speed
        self.fuel = model_info.max_fuel
        self.max_fueld = model_info.max_fuel
        
def perform_system_action():
    action = get_selection(global_state.page, 2)
    if (action == "Retire Pilot"):
        change_cursor("Game Over")
    elif (action == "Exit Game"):
        change_cursor("Game Over")
    elif (action == "High Scores"):
        change_cursor("High Scores")

'''

function tradeInShip()
	local shipId = getSelection(page, 3)
	local cost = shipList[shipId].cost
	local tradeInCredit = shipResaleFactor * player.ship.cost
	if (player.credits >= cost - tradeInCredit) then
		player.ship = newShip(shipId)
		player.credits = player.credits - cost + tradeInCredit
		return true
	else
		return false, { "You do not have enough credits to trade in." }
	end
end

function purchaseServices()
	local action = getSelection(page, 2)
	local cost = serviceList[action].cost
	if (player.credits >= cost) then
		if (action == "Repair Ship") then
			if (player.ship.hullHealth < player.ship.hullStrength) then
				player.ship.hullHealth = player.ship.hullStrength
				player.credits = player.credits - cost
				return true
			else
				return false, { "Your ship is already fully repaired." }
			end
		elseif (action == "Refuel Ship") then
			if (player.ship.fuel < player.ship.maxFuel) then
				player.ship.fuel = player.ship.maxFuel
				player.credits = player.credits - cost
				return true
			else
				return false, { "Your ship is already fully fueled." }
			end
		elseif (action == "Sleep 1 Night") then
			passDays(1)
			player.credits = player.credits - cost
			return true
		elseif (action == "Sleep 10 Nights") then
			passDays(10)
			player.credits = player.credits - cost
			return true
		end
	else
		return false, { "You do not have the required funds." }
	end
end

function engageEnemy()
	local action = getSelection(page, 2)
	if (action == "Flee") then
		local flightChance = 1 - math.min(0.8, player.ship.speed / warp.enemyShip.speed)
		if (math.random() < flightChance) then
			clearWarpEncounter()
			return true, { "You escaped successfully." }
		else
			performAttack(warp.enemyShip, player.ship)
			if (player.ship.hullHealth <= 0) then
				changeCursor("Game Over")
				return true, { "You were killed while fleeing." }
			end
		end
	elseif (action == "Surrender") then
		player.credits = math.ceil(player.credits * 0.1)
		for i, commodity in ipairs(commodityList) do
			player.commodities[commodity.name] = 0
		end
		clearWarpEncounter()
		return true, { "You surrendered." }
	else
		performAttack(player.ship, warp.enemyShip)
		if (warp.enemyShip.hullHealth <= 0) then
			clearWarpEncounter()
			return true, { "You defeated the enemy." }
		else
			performAttack(warp.enemyShip, player.ship)
			if (player.ship.hullHealth <= 0) then
				changeCursor("Game Over")
				return true, { "You were killed in the fight." }
			end
		end
	end
	return true
end

function hasContraband(player)
	for i, stock in ipairs(player.commodities) do
		if (stock > 0 and not commodityList[i].legal) then
			return true
		end
	end
	return false
end

function enterPirateCheck()
	local enemyShipType = "Llama"
	warp.enemyShip = newShip(enemyShipType)
	warp.hasEncounter = true
	addPopUp({ "You encounter a pirate ship,", "demanding credits." })
	changeCursor("Pirate Encounter")
end

function enterCopCheck()
	local enemyShipType = "Llama"
	warp.enemyShip = newShip(enemyShipType)
	warp.hasEncounter = true
	addPopUp({ "You encounter a cop ship,", "demanding credits." })
	changeCursor("Cop Encounter")
end

function clearWarpEncounter()
	warp.enemyShip = nil
	warp.guaranteeSafe = true
	page = nil
end

function continueWarp()
	local guaranteeSafe = warp.guaranteeSafe
	local source = warp.source
	local target = warp.target
	local copCheckChance
	local pirateCheckChance
	if (guaranteeSafe) then
		warp.guaranteeSafe = false
		copCheckChance = 0
		pirateCheckChance = 0
	else
		copCheckChance = math.max(source.lawfulRating, target.lawfulRating) * 0.25
		pirateCheckChance = (1 - math.min(source.lawfulRating, target.lawfulRating)) * 0.25
	end
	if (warp.iter <= warp.maxIters) then
		if (math.random() < copCheckChance and hasContraband(player)) then
			enterCopCheck()
		elseif (math.random() < pirateCheckChance) then
			enterPirateCheck()
		else
			page = nil
			player.ship.fuel = player.ship.fuel - 1
			warp.iter = warp.iter + 1
		end
	else
		changeCursor("Commodities")
		player.planet = target
		if (warp.hasEncounter) then
			addPopUp({ "The rest of your trip is uneventful." })
		else
			addPopUp({ "Your trip is uneventful." })
		end
		warp = nil
	end
end

function getDistortedRelativeCoordinates(planet1, planet2)
	local deltaX = planet1.coordinates.x - planet2.coordinates.x
	local deltaY = planet1.coordinates.y - planet2.coordinates.y
	local scaleFactorX, scaleFactorY
	if (deltaX == 0) then
		scaleFactorX = 0
	else
		scaleFactorX = math.max(0, 12 * math.log(math.abs(deltaX) / 16)) / math.abs(deltaX)
	end
	if (deltaY == 0) then
		scaleFactorY = 0
	else
		scaleFactorY = math.max(0, 3 * math.log(math.abs(deltaY) / 16)) / math.abs(deltaY)
	end
	return deltaX * scaleFactorX, deltaY * scaleFactorY
end

function calculatePlanetDistance(planet1, planet2)
	local x1, y1, x2, y2
	x1 = planet1.coordinates.x
	y1 = planet1.coordinates.y
	x2 = planet2.coordinates.x
	y2 = planet2.coordinates.y
	return math.sqrt((y2-y1)*(y2-y1)+(x2-x1)*(x2-x1))
end

function performPlanetAction()
	local action = getSelection(page, 2)
	local target = getSelection(page, 3)
	local result, msg
	if (action == "Warp Here") then
		result, msg = initiateWarp()
	else
		msg = getPlanetNews(target)
		result = true
	end
	return result, msg
end

function initiateWarp()
	local planet = player.planet
	local id = getSelection(page, 3)
	local targetPlanet = planetList[id]
	local distance = calculatePlanetDistance(planet, targetPlanet)
	local maxIters = math.ceil(distance / 10)
	if (player.ship.fuel >= maxIters) then
		passDays(1)
		page = nil
		popUps = nil
		warp = {
			source=planet,
			target=targetPlanet,
			maxIters=maxIters,
			iter=1,
			hasEncounter=false,
			guaranteeSafe = true
		}
		warp.guaranteeSafe = true
		return true
	else
		return false, { "You do not have the required fuel." }
	end
end

function getNeighbors(planetId)
	local result = {}
	for i, con in ipairs(planetConnections) do
		if (con[1] == planetId) then
			table.insert(result, con[2])
		elseif (con[2] == planetId) then
			table.insert(result, con[1])
		end
	end
	return result
end

function changeCursor(pageId)
	local planetOptions = { "Commodities", "Planets", "Services", "Shipyard", "System" }

	if (pageId == "Commodities") then
		page = {
			id = "Commodities",
			level = 1,
			options = {
				planetOptions,
				{ "Buy 1", "Buy 5", "Buy 10", "Buy Max", "Sell 1", "Sell 5", "Sell 10", "Sell Max" },
				player.planet.commodityList
			},
			selections = {
				1,
				1,
				1
			}
		}
	elseif (pageId == "Planets") then
		page = {
			id = "Planets",
			level = 1,
			options = {
				planetOptions,
				{ "Warp Here", "See News" },
				getNeighbors(player.planet.name)
			},
			selections = {
				2,
				1,
				1
			}
		}
	elseif (pageId == "Services") then
		page = {
			id = "Services",
			level = 1,
			options = {
				planetOptions,
				{ "Repair Ship", "Refuel Ship", "Sleep 1 Night", "Sleep 10 Nights" }
			},
			selections = {
				3,
				1
			}
		}
	elseif (pageId == "Shipyard") then
		page = {
			id = "Shipyard",
			level = 1,
			options = {
				planetOptions,
				{ "Buy Ship" },
				player.planet.shipyard
			},
			selections = {
				4,
				1,
				1
			}
		}
	elseif (pageId == "System") then
		page = {
			id = "System",
			level = 1,
			options = {
				planetOptions,
				{ "Exit Game", "Retire Pilot", "High Scores" }
			},
			selections = {
				5,
				1
			}
		}
	elseif (pageId == "Pirate Encounter") then
		page = {
			id = "Pirate Encounter",
			level = 2,
			options = {
				{ "Pirate Encounter" },
				{ "Flee", "Surrender", "Attack" }
			},
			selections = {
				1,
				1
			}
		}
	elseif (pageId == "Cop Encounter") then
		page = {
			id = "Cop Encounter",
			level = 2,
			options = {
				{ "Cop Encounter" },
				{ "Flee", "Surrender", "Attack" }
			},
			selections = {
				1,
				1
			}
		}
	elseif (pageId == "Game Over") then
		page = {
			id = "Game Over",
			level = 2,
			options = {
				{ "Game Over" },
				{ "Try Again" }
			},
			selections = {
				1,
				1
			}
		}
		if (not checkHighScore()) then
			printHighScores()
		end
	end
end

function getSelection(page, level)
	local options = page.options[level]
	return options[page.selections[level]]
end

function transactCommodity()
	local planet = player.planet
	local id = getSelection(page, 3)
	local action = getSelection(page, 2)
	local amount, buyer, seller, unitCost
	if (action == "Buy 1" or action == "Buy 5" or action == "Buy 10") then
		if (action == "Buy 1") then
			amount = 1
		elseif (action == "Buy 5") then
			amount = 5
		else
			amount = 10
		end
		buyer = player
		seller = planet
		unitCost = math.floor(planet.commodityRates[id] * commodityList[id].baseCost)
	elseif (action == "Buy Max") then
		buyer = player
		seller = planet
		unitCost = math.floor(planet.commodityRates[id] * commodityList[id].baseCost)
		if (math.floor(buyer.credits / unitCost) < seller.commodities[id]) then
			amount = math.floor(buyer.credits / unitCost)
		else
			amount = seller.commodities[id]
		end
	elseif (action == "Sell 1" or action == "Sell 5" or action == "Sell 10") then
		if (action == "Sell 1") then
			amount = 1
		elseif (action == "Sell 5") then
			amount = 5
		else
			amount = 10
		end
		buyer = planet
		seller = player
		unitCost = math.floor(planet.commodityRates[id] * commodityList[id].baseCost * commodityResaleFactor)
	else
		buyer = planet
		seller = player
		unitCost = math.floor(planet.commodityRates[id] * commodityList[id].baseCost * commodityResaleFactor)
		if (math.floor(buyer.credits / unitCost) < seller.commodities[id]) then
			amount = math.floor(buyer.credits / unitCost)
		else
			amount = seller.commodities[id]
		end
	end
	if (buyer.credits < unitCost * amount) then
		return false, { "You do not have the required credits." }
	elseif (seller.commodities[id] < amount) then
		return false, { "There is nothing to buy or sell." }
	else
		buyer.commodities[id] = buyer.commodities[id] + amount
		seller.commodities[id] = seller.commodities[id] - amount
		buyer.credits = buyer.credits - unitCost * amount
		seller.credits = seller.credits + unitCost * amount
		return true
	end
end

function attemptAction()
	local actionSuccessful, msg
	if (page.id == "Commodities") then
		actionSuccessful, msg = transactCommodity()
	elseif (page.id == "Planets") then
		actionSuccessful, msg = performPlanetAction()
	elseif (page.id == "Services") then
		actionSuccessful, msg = purchaseServices()
	elseif (page.id == "Shipyard") then
		actionSuccessful, msg = tradeInShip()
	elseif (page.id == "System") then
		actionSuccessful, msg = performSystemAction()
	elseif (page.id == "Pirate Encounter" or page.id == "Cop Encounter") then
		actionSuccessful, msg = engageEnemy()
	elseif (page.id == "Game Over") then
		actionSuccessful, msg = newGame()
	end
	return actionSuccessful, msg
end

function addPopUp(msgList)
	if (popUps == nil) then
		popUps = {}
	end
	table.insert(popUps, msgList)
end

function removePopUp()
	table.remove(popUps)
	if (#popUps == 0) then
		popUps = nil
	end
end

function onButtonPress(inputCommand)
	if (inputCommand == "exit") then
		exitGame = true
	elseif (newHighScore ~= nil) then
		if (inputCommand == "up") then
			if (newHighScore.y == 1) then
				newHighScore.name[newHighScore.x] = numToChar((charToNum(newHighScore.name[newHighScore.x])) % #alphaNumMap + 1)
			else
				newHighScore.y = 2
			end
		elseif (inputCommand == "down") then
			if (newHighScore.y == 1) then
				newHighScore.name[newHighScore.x] = numToChar((charToNum(newHighScore.name[newHighScore.x]) - 2 + #alphaNumMap) % #alphaNumMap + 1)
			end
		elseif (inputCommand == "left") then
			if (newHighScore.y == 1) then
				newHighScore.x = (newHighScore.x + 1) % 3 + 1
			end
		elseif (inputCommand == "right") then
			if (newHighScore.y == 1) then
				newHighScore.x = newHighScore.x % 3 + 1
			end
		elseif (inputCommand == "a") then
			if (newHighScore.y == 1) then
				newHighScore.y = 2
			else
				if (#highScores == 5) then
					highScores[5] = newHighScore
				else
					table.insert(highScores, newHighScore)
				end
				newHighScore = nil
				sortHighScores()
				saveHighScores()
				printHighScores()
			end
		elseif (inputCommand == "b") then
			if (newHighScore.y == 2) then
				newHighScore.y = 1
			end
		end
	elseif (popUps ~= nil and #popUps > 0) then
		if (inputCommand == "a") then
			table.remove(popUps)
		end
	elseif (inputCommand == "up") then
		if (page.level < #page.options) then
			page.level = page.level + 1
		end
	elseif (inputCommand == "down") then
		local minLevel = 1
		if (#page.options[1] <= 1) then
			minLevel = 2
		end
		if (page.level > minLevel) then
			page.level = page.level - 1
		end
	elseif (inputCommand == "left") then
		if (page.selections[page.level] > 1) then
			page.selections[page.level] = page.selections[page.level] - 1
		else
			page.selections[page.level] = #page.options[page.level]
		end
		if (page.level == 1) then
			changeCursor(page.options[1][page.selections[1]])
		end
	elseif (inputCommand == "right") then
		if (page.selections[page.level] < #page.options[page.level]) then
			page.selections[page.level] = page.selections[page.level] + 1
		else
			page.selections[page.level] = 1
		end
		if (page.level == 1) then
			changeCursor(page.options[1][page.selections[1]])
		end
	elseif (inputCommand == "a" and page ~= nil) then
		if (page.level == 2) then
			local actionSuccessful, msg = attemptAction()
			if (msg ~= nil) then
				addPopUp(msg)
			end
		else
			page.level = 2
		end
	elseif (inputCommand == "b") then
		exitGame = false
	elseif (inputCommand == "x") then
		exitGame = false
	elseif (inputCommand == "y") then
		exitGame = false
	end
end

exitGame = false
gameLoaded = false
highScores = {}
alphaNumMap = { " ", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "!", "?", "." }
'''
